#include "param.h"
#include "types.h"
#include "memlayout.h"
#include "riscv.h"
#include "defs.h"

/*
 * the kernel's page table.
 */
pagetable_t kernel_pagetable;

extern char etext[];  // kernel.ld sets this to end of kernel code.

// Make a direct-map page table for the kernel.
// 创建内核的页表
pagetable_t
kvmmake(void)
{
  pagetable_t kpgtbl;

  kpgtbl = (pagetable_t) kalloc();
  memset(kpgtbl, 0, PGSIZE);

  // uart registers
  kvmmap(kpgtbl, UART0, UART0, PGSIZE, PTE_R | PTE_W);

  // virtio mmio disk interface
  kvmmap(kpgtbl, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);

  // PLIC
  kvmmap(kpgtbl, PLIC, PLIC, 0x4000000, PTE_R | PTE_W);

  // map kernel text executable and read-only.
  // 内核的代码段（.text）
  kvmmap(kpgtbl, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);

  // map kernel data and the physical RAM we'll make use of.
  // 内核的数据段（.data 和 .bss）以及后续分配的物理内存
  kvmmap(kpgtbl, (uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);

  return kpgtbl;
}

// add a mapping to the kernel page table.
// only used when booting.
// does not flush TLB or enable paging.
void
kvmmap(pagetable_t kpgtbl, uint64 va, uint64 pa, uint64 sz, int perm)
{
  if(mappages(kpgtbl, va, sz, pa, perm) != 0)
    panic("kvmmap");
}

// Initialize the kernel_pagetable, shared by all CPUs.
void
kvminit(void)
{
  kernel_pagetable = kvmmake();
}

// Switch the current CPU's h/w page table register to
// the kernel's page table, and enable paging.
void
kvminithart()
{
  //vmprint(kernel_pagetable, 1);

  // wait for any previous writes to the page table memory to finish.
  sfence_vma();

  w_satp(MAKE_SATP(kernel_pagetable));

  // flush stale entries from the TLB.
  sfence_vma();
}

// Return the address of the PTE in page table pagetable
// that corresponds to virtual address va.  If alloc!=0,
// create any required page-table pages.
//
// The risc-v Sv39 scheme has three levels of page-table
// pages. A page-table page contains 512 64-bit PTEs.
// A 64-bit virtual address is split into five fields:
//   39..63 -- must be zero.
//   30..38 -- 9 bits of level-2 index.
//   21..29 -- 9 bits of level-1 index.
//   12..20 -- 9 bits of level-0 index.
//    0..11 -- 12 bits of byte offset within the page.
// pagetable: 根页表
// 在TLB中已经有了walk这一功能，分页、创建页表项、三级页表的维护实际上都是应该由硬件完成的.
// 而这里还要有walk的原因是，首先它要创建最初的三级页表，其次就是有时内核执行用户态内核态拷贝时会使用用户页表进行读取数据，并拷贝到内核中，此时可能需要手动模拟walk
// level 2 ~ 1 的PPN是下一级页表的物理地址
// level 0(叶子pte)的PPN是page的物理地址
pte_t *
walk(pagetable_t pagetable, uint64 va, int alloc)
{
  if(va >= MAXVA)
    panic("walk");

  for(int level = 2; level > 0; level--) { // 处理level 2,1
    // 页表项pte=物理页号 (Physical Page Number, PPN, 44位) + 标志位 (Flags, 10位)
    //
    // 在C语言中，指针和数组在访问元素时可以使用相同的语法。pagetable_t 是一个指向 uint64 的指针，而指针可以像数组一样使用下标 [] 来操作.
    // `pagetable[PX(level, va)]`=`*(pagetable + PX(level, va))`表示从 pagetable 指针的地址开始，向前移动 PX(level, va) 个元素（而不是字节）。因为 pagetable 是一个 uint64 指针，所以每移动一步，地址就会增加 sizeof(uint64) 个字节
    pte_t *pte = &pagetable[PX(level, va)];
    if(*pte & PTE_V) { // 如果存在pte并且有效，合法，从PTE中取出下一级页表物理地址并替换pagetable
      pagetable = (pagetable_t)PTE2PA(*pte);
    } else {
      // 不存在pte，或者该pte已经是无效pte
      // 如果允许分配则尝试为该pte分配新的下一级页表
      if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)
        return 0;
      memset(pagetable, 0, PGSIZE);
      *pte = PA2PTE(pagetable) | PTE_V;
    }
  }
  return &pagetable[PX(0, va)];
}

// Look up a virtual address, return the physical address,
// or 0 if not mapped.
// Can only be used to look up user pages.
uint64
walkaddr(pagetable_t pagetable, uint64 va)
{
  pte_t *pte;
  uint64 pa;

  if(va >= MAXVA)
    return 0;

  pte = walk(pagetable, va, 0);
  if(pte == 0)
    return 0;
  if((*pte & PTE_V) == 0)
    return 0;
  if((*pte & PTE_U) == 0)
    return 0;
  pa = PTE2PA(*pte);
  return pa;
}

// Create PTEs for virtual addresses starting at va that refer to
// physical addresses starting at pa.
// va and size MUST be page-aligned.
// Returns 0 on success, -1 if walk() couldn't
// allocate a needed page-table page.
int
mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm)
{
  uint64 a, last;
  pte_t *pte;

  if((va % PGSIZE) != 0)
    panic("mappages: va not aligned");

  if((size % PGSIZE) != 0)
    panic("mappages: size not aligned");

  if(size == 0)
    panic("mappages: size");

  a = va;
  last = va + size - PGSIZE; // 要映射的最后一个页面的虚拟地址
  for(;;){
    // walk: 遍历页表树（对于 RV64 是三级）来找到正确的 PTE. 1, 告诉 walk，如果中间的页表页不存在，就自动创建它们
    if((pte = walk(pagetable, a, 1)) == 0)
      return -1;
    if(*pte & PTE_V) // 检查找到的 PTE 是否已经有效（即 PTE_V 位是否已设置）。如果有效，说明该虚拟地址已经被映射过了. mappages 不允许重复映射
      panic("mappages: remap");
    // 将va对应的pa转换成PTE形式, 并加上权限，以及有效flag
    *pte = PA2PTE(pa) | perm | PTE_V; // 设置 PTE_V 位，让 MMU 知道这个页表项是有效的，可以用来进行地址转换
    if(a == last)
      break;
    a += PGSIZE;
    pa += PGSIZE;
  }
  return 0;
}

static char *point[] = {
  [0] = "..",
  [1] = ".. ..",
  [2] = ".. .. ..",
};

void vmprint(pagetable_t pagetable,uint64 level){
  //打印最顶层的页表信息
  if(level == 1){
    printf("page table %p\n",pagetable);
  }
  //遍历页表项 2^9 = 512 所以一个页表中应该有512个页表项
  for(int i = 0;i<512;i++){
    pte_t pte = pagetable[i];
    if(pte & PTE_V){
        //由方法名可以得出这个child其实就是pte对应的物理地址
        uint64 child = PTE2PA(pte);
        printf("%s%d: pte %p pa %p\n",point[level-1],i,(void *)pte, (void *)child);
        //页表树层级总共就三层，再继续递归会导致出错
        if(level!=3){
          vmprint((pagetable_t)child,level+1);
        }
    }
  }
}
